#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

#include "../comm/httplib.h"
#include "../comm/log.hpp"
#include "../comm/util.hpp"
#include "oj_model_mysql.hpp"
#include "oj_view.hpp"
#include <jsoncpp/json/json.h>

namespace ns_controller
{
    using namespace ns_log;
    using namespace ns_util;
    using namespace ns_model;
    using namespace ns_view;
    using namespace httplib;

    // 主机的各个参数
    struct Machine
    {
    public:
        std::string ip; // 主机的ip
        int port; // 端口号
        uint64_t load; // 负载
        std::mutex* mtx; // mtx禁止拷贝，所以使用指针
    public:
        Machine()
        :ip("")
        ,port(0)
        ,load(0)
        ,mtx(nullptr)    
        {}

        ~Machine()
        {
            // 下面在读取设备文件的时候会创建临时的Machine，再将临时的浅拷贝到数组中
            // 如果在此处进行释放，会直接释放掉原本的锁，可以考虑用move或者引用计数来实现delete
            // TODO
            // delete mtx;
        }

        void AddLoad()
        {
            if(mtx)
                mtx->lock();
            load++;
            if(mtx)
                mtx->unlock();
        }

        void DecLoad()
        {
            if(mtx)
                mtx->lock();
            if(load != 0) load--;
            if(mtx)
                mtx->unlock();
        }

        uint64_t GetLoad()
        {
            uint64_t _load = 0;
            if (mtx) mtx->lock();
            _load = load;
            if (mtx) mtx->unlock();
            return _load;
        }
        // 将load归零
        void ResetLoad()
        {
            if(mtx)
                mtx->lock();
            load = 0;
            if(mtx)
                mtx->unlock();
        }
    };

    const std::string service_conf_path = "./conf/service.conf";
    // 负载均衡器
    class LoadBlance
    {
    private:
        std::vector<Machine> _machines; // 所有机器信息
        std::vector<int> _online; // 上线机器的编号
        std::vector<int> _offline; // 下线机器的编号
        std::mutex _mtx;

    public:

        LoadBlance()
        {
            assert(LoadConf(service_conf_path));
            LOG(INFO) << "加载主机配置文件成功" << std::endl;
        }

        bool LoadConf(const std::string& conf_path)
        {
            std::ifstream ifs(conf_path);
            if(!ifs.is_open())
            {
                LOG(FATAL) << "主机配置加载失败" << std::endl;
                return false;
            }

            std::string line;
            while(std::getline(ifs, line))
            {
                std::vector<std::string> ret;
                StringUtil::SpiltString(line, &ret, " ");
                if(ret.size() != 2)
                {
                    LOG(ERROR) << "主机配置文件格式错误：" << line << std::endl;
                    continue; 
                }
                Machine m;
                m.ip = ret[0];
                m.port = std::stoi(ret[1]);
                m.load = 0;
                m.mtx = new std::mutex();
                // 一键上线和加载配置
                _online.push_back(_machines.size());
                _machines.push_back(m);
            }
            ifs.close();
            return true;
        }

        bool SmartSelect(int* id, Machine*& mhe)
        {
            _mtx.lock();
            if(_online.empty())
            {
                _mtx.unlock();
                LOG(FATAL) << "服务器全部下线，无服务器可使用" << std::endl;
                
                return false;
            }
            // 轮询 + hash
            int _id = _online[0]; 
            uint64_t _load = _machines[_id].GetLoad();
            
            // 查找负载最小的机器
            for(int i = 1; i < _online.size(); ++i)
            {
                uint64_t other = _machines[_online[i]].GetLoad();
                if(_load > other)
                {
                    _load = other;
                    _id = _online[i];
                }
            }

            *id = _id;
            mhe = &_machines[_id];
            _mtx.unlock();
            return true;
        }

        void OnlineMachine()
        {
            // 将配置中的文件统一上线
            _mtx.lock();
            _online.insert(_online.end(), _offline.begin(), _offline.end());
            _offline.clear();
            _mtx.unlock();
            LOG(INFO) << "已上线所有主机" << std::endl;
        }

        void OfflineMachine(int id)
        {
            _mtx.lock();
            for(auto iter = _online.begin(); iter != _online.end(); ++iter)
            {
                if(*iter == id)
                {
                    _machines[id].ResetLoad();
                    _online.erase(iter);
                    _offline.push_back(id);
                    break;
                }
            }
            _mtx.unlock();
        }

        // 调试使用，便于观察上线和下线主机
        void ShowMachine()
        {
            _mtx.lock();
            std::cout << "目前上线的主机：";
            for(int e : _online)
                std::cout << e << " ";
            std::cout << std::endl;

            std::cout << "目前下线的主机：";
            for(int e : _offline)
                std::cout << e << " ";
            std::cout << std::endl;
            _mtx.unlock();
        }
    };

    const std::string ctemplate_path = "./ctemplate_html/";
    // 核心业务逻辑控制器
    class Controller
    {
    private:
        Model _model; // 题库数据交互模块
        View _view; // HTML数据渲染模块
        LoadBlance _loadblance; // 负载均衡模块

    public:
        // 获取题目列表的HTML
        bool AllQuestions(std::string* out_html)
        {
            std::vector<Question> questions;
            if(_model.GetAllQuestions(&questions))
            {
                // 题目获取成功
                std::sort(questions.begin(), questions.end(), [](const Question& q1, const Question& q2){
                    return q1.number < q2.number;
                });
                _view.ExpendAllQuestion(questions, out_html);
                return true;
            }
            else
            {
                LOG(FATAL) << "题库获取失败，请检查题库是否存在" << std::endl;
                *out_html = "题库获取失败，请检查题库是否存在";
                return false;
            }

        }
        // 获取一道题目单独信息的HTML
        bool OneQuestion(const std::string& number, std::string* out_html)
        {
            Question q;
            if(_model.GetOneQuestion(number, &q))
            {
                // 获取一道题信息成功
                _view.ExpendOneQuestion(q, out_html);
                return true;
            }
            else 
            {
                LOG(ERROR) << "获取" << number << "题目失败" << std::endl;
                *out_html = "获取题目" + number + "失败";
                return false;
            }
        }
        // 判题逻辑
        void Judge(const std::string& number, const std::string& in_json, std::string* out_json)
        {
            // 1. 通过题号拿到题目细节
            Question q;
            _model.GetOneQuestion(number, &q);

            // 2. 通过in_json得到用户提交的代码
            Json::Value inval;
            Json::Reader reader;
            reader.parse(in_json, inval);
            std::string code;
            code = inval["code"].asString();

            // 3. 拼接header和tail
            Json::Value outval; // 向编译服务器传递的json串
            Json::FastWriter writer;
            outval["code"] = code + "\n" + q.tail;
            outval["input"] = "";
            outval["cpu_limit"] = q.cpu_limit;
            outval["mem_limit"] = q.mem_limit;
            std::string complie_json = writer.write(outval);

            // 4. 选择主机，向编译服务器传递信息 + 差错处理
            // 连续选择，直到选中或者失败
            
            while(true)
            {
                int id = -1;
                Machine* mac = nullptr;
                // 智能选择负载最少的机器
                if(!_loadblance.SmartSelect(&id, mac))
                {
                   // 选择失败--直接退出，报错在函数中已经给出
                   break;
                }
                // 选择成功
                _loadblance.ShowMachine();
                Client clt(mac->ip, mac->port);
                mac->AddLoad();
                LOG(INFO) << "智能选择成功，选择的主机ip：" << mac->ip << " ，port：" << mac->port <<  "，目前的负载为："<< mac->GetLoad() << std::endl;
                // 向编译服务发起HTTP请求
                if(Result res = clt.Post("/compile_run", complie_json, "application/json, charset=utf-8"))
                {
                    // 请求有返回值
                    if(res->status == 200)
                    {
                        // 请求成功
                        LOG(INFO) << "请求编译运行服务成功! " << std::endl;
                        *out_json = res->body;
                        mac->DecLoad();
                        break;
                    }
                }
                else
                {
                    // 请求失败
                    LOG(ERROR) << "请求服务器失败，服务器可能下线：" << id << " " << mac->ip << " "  << mac->port << std::endl;
                    _loadblance.OfflineMachine(id);
                    _loadblance.ShowMachine();
                }
            }
        }

        void OnlineMachines()
        {
            _loadblance.OnlineMachine();
        }
    };
}